package com.bigdata.demo.service.search;

import com.bigdata.demo.entity.CRReport;
import com.bigdata.demo.repository.CRReportRepository;
import com.bigdata.demo.service.ServiceMultiResult;
import com.bigdata.demo.service.ServiceResult;
import com.bigdata.demo.web.dto.CRDTO;
import com.bigdata.demo.web.form.CRReportSearch;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeAction;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequestBuilder;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.index.reindex.DeleteByQueryRequestBuilder;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
public class SearchServiceImpl implements ISearchService  {

    private static final Logger logger = LoggerFactory.getLogger(ISearchService.class);
    private static final String INDEX_NAME = "cr";

    private static final String INDEX_TYPE = "crreport";

    @Autowired
    private TransportClient esClient;

    @Autowired
    private CRReportRepository crReportRepository;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ModelMapper modelMapper;


    public void createOrUpdateIndex(CRIndexMessage message) {
        String id_key_cr = message.getId_key_cr();

        CRReport crReport = crReportRepository.findById(id_key_cr);
        if (crReport == null) {
            logger.error("Index id_key_cr {} dose not exist!", id_key_cr);
            this.index(id_key_cr, message.getRetry() + 1);
            return;
        }

        CRIndexTemplate indexTemplate = new CRIndexTemplate();
        modelMapper.map(crReport, indexTemplate);
        indexTemplate.setId_key_cr(id_key_cr);

        SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME).setTypes(INDEX_TYPE)
                .setQuery(QueryBuilders.termQuery(CRIndexKey.ID_KEY_CR, id_key_cr));

        logger.debug(requestBuilder.toString());
        SearchResponse searchResponse = requestBuilder.get();

        boolean success;
        long totalHit = searchResponse.getHits().getTotalHits();
        if (totalHit == 0) {
            success = create(indexTemplate);
        } else if (totalHit == 1) {
            String esId = searchResponse.getHits().getAt(0).getId();
            success = update(esId, indexTemplate);
        } else {
            success = deleteAndCreate(totalHit, indexTemplate);
        }


        if(!success)
            logger.error("Error when Update index with cr: " + indexTemplate.getId_key_cr());


    }



    private boolean deleteAndCreate(long totalHit, CRIndexTemplate indexTemplate) {
        DeleteByQueryRequestBuilder builder = DeleteByQueryAction.INSTANCE
                .newRequestBuilder(esClient)
                .filter(QueryBuilders.termQuery(CRIndexKey.ID_KEY_CR, indexTemplate.getId_key_cr()))
                .source(INDEX_NAME);

        logger.debug("Delete by query for CR: " + builder);

        BulkByScrollResponse response = builder.get();
        long deleted = response.getDeleted();
        if (deleted != totalHit) {
            logger.warn("Need delete {}, but {} was deleted!", totalHit, deleted);
            return false;
        } else {
            return create(indexTemplate);
        }
    }

    private boolean update(String esId, CRIndexTemplate indexTemplate) {
        if (!updateSuggest(indexTemplate)) {
            return false;
        }

        try {
            UpdateResponse response = this.esClient.prepareUpdate(INDEX_NAME, INDEX_TYPE, esId).setDoc(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON).get();

            logger.debug("Update index with cr: " + indexTemplate.getId_key_cr());
            if (response.status() == RestStatus.OK) {
                return true;
            } else {
                return false;
            }
        } catch (JsonProcessingException e) {
            logger.error("Error to index cr " + indexTemplate.getId_key_cr(), e);
            return false;
        }
    }

    private boolean create(CRIndexTemplate indexTemplate) {
        if (!updateSuggest(indexTemplate)) {
            return false;
        }

        try {
            IndexResponse response = this.esClient.prepareIndex(INDEX_NAME, INDEX_TYPE)
                    .setSource(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON).get();

            logger.debug("Create index with cr: " + indexTemplate.getId_key_cr());
            if (response.status() == RestStatus.CREATED) {
                return true;
            } else {
                return false;
            }
        } catch (JsonProcessingException e) {
            logger.error("Error to index house " + indexTemplate.getId_key_cr(), e);
            return false;
        }
    }
    private boolean updateSuggest(CRIndexTemplate indexTemplate) {
        AnalyzeRequestBuilder requestBuilder = new AnalyzeRequestBuilder(
                this.esClient, AnalyzeAction.INSTANCE, INDEX_NAME, indexTemplate.getCr_subject());

        requestBuilder.setAnalyzer("ik_smart");

        AnalyzeResponse response = requestBuilder.get();
        List<AnalyzeResponse.AnalyzeToken> tokens = response.getTokens();
        if (tokens == null) {
            logger.warn("Can not analyze token for house: " + indexTemplate.getId_key_cr());
            return false;
        }

        List<CRSuggest> suggests = new ArrayList<>();
        for (AnalyzeResponse.AnalyzeToken token : tokens) {
            // 排序数字类型 & 小于2个字符的分词结果
            if ("<NUM>".equals(token.getType()) || token.getTerm().length() < 2) {
                continue;
            }
            String str = token.getTerm().toUpperCase();
            if (isValidKKS(str)) {
                Set<String> list = getRelatedKKS(str);
                String crsubject = indexTemplate.getCr_subject();
                int indexx = crsubject.indexOf(str);
                if (indexx != -1) {
                    String oll = crsubject.substring(indexx);
                    CRSuggest suggest = new CRSuggest();
                    suggest.setInput(oll);
                    suggests.add(suggest);

                    for (String s1 : list) {
                        suggest = new CRSuggest();
                        suggest.setInput(crsubject.replace(str, s1));
                        suggests.add(suggest);
                    }
                }
            }
        }
            indexTemplate.setSuggest(suggests);
            return true;

    }
    /*public void showSuggest(CRReport cr) {
        AnalyzeRequestBuilder requestBuilder = new AnalyzeRequestBuilder(
                this.esClient, AnalyzeAction.INSTANCE, INDEX_NAME,cr.getCr_subject(),cr.getCr_cause());

        requestBuilder.setAnalyzer("ik_smart");
        //requestBuilder.setAnalyzer("ik_max_word");

        AnalyzeResponse response = requestBuilder.get();
        List<AnalyzeResponse.AnalyzeToken> tokens = response.getTokens();
        if (tokens == null) {
            logger.warn("Can not analyze token for house: " + cr.getId());
        }

        Set<String> finalset=new  HashSet<>();
        for (AnalyzeResponse.AnalyzeToken token : tokens) {
            // 排序数字类型 & 小于2个字符的分词结果
            if ("<NUM>".equals(token.getType()) || token.getTerm().length() < 2) {
                continue;
            }

            String str=token.getTerm().toUpperCase();
            if(isValidKKS(s)){
                Set<String> list=getRelatedKKS(s);
                String crsubject= cr.getCr_subject();
                int indexx=crsubject.indexOf(s);
                if(indexx!=-1){
                    String oll=crsubject.substring(indexx);
                    finalset.add(oll);
                    for(String s1:list)
                        finalset.add(crsubject.replace(s,s1));
                }
            }
        }

    }*/

    private void index(String id, int retry) {
        if (retry > CRIndexMessage.MAX_RETRY) {
            logger.error("Retry index times over 3 for cr: " + id + " Please check it!");
            return;
        }

        CRIndexMessage message = new CRIndexMessage(id, CRIndexMessage.INDEX, retry);
        try {
            this.createOrUpdateIndex(message);
        } catch ( Exception e) {
            logger.error("Json encode error for " + message);
        }

    }



    public boolean isValidKKS(String str){
        if(str.length()>3&&crReportRepository.findKKSCount(str)==1)
            return true;
        else
            return false;
    }


    public Set<String> getRelatedKKS(String kks){


        Set<String> stringSet=new HashSet<String>();

        if(kks.startsWith("0")||kks.startsWith("1")||kks.startsWith("2")||kks.startsWith("9")){
            String cc =kks.substring(1,kks.length());
            if(cc!=null&&cc.length()>1)
                stringSet.add(cc);
        }

        if(!kks.startsWith("0")&&!kks.startsWith("1")&&!kks.startsWith("2")&&!kks.startsWith("9")){
            stringSet.add("1"+kks);
            stringSet.add("2"+kks);
        }
        if(kks.startsWith("1")){
            String cc ="2"+kks.substring(1,kks.length());
            if(cc!=null&&cc.length()>1)
                stringSet.add(cc);
        }
        if(kks.startsWith("2")){
            String cc ="1"+kks.substring(1,kks.length());
            if(cc!=null&&cc.length()>1)
                stringSet.add(cc);
        }
        return stringSet;

    }


    @Override
    public ServiceMultiResult<CRDTO> query(CRReportSearch crReportSearch) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if(crReportSearch.getKeyword()==null)
            crReportSearch.setKeyword("");

        List<CRDTO> list=new ArrayList<>();
        boolQuery.must(
                QueryBuilders.multiMatchQuery(crReportSearch.getKeyword(),
                        CRIndexKey.CR_SUBJECT
                ));

        SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME)
                .setTypes(INDEX_TYPE)
                .setQuery(boolQuery)
                .addSort(
                        "_score",
                        SortOrder.fromString("desc")
                )
                .setFrom(crReportSearch.getStart())
                .setSize(crReportSearch.getSize())
                .setFetchSource(CRIndexKey.ID_KEY_CR, null);

        List<String> crIds = new ArrayList<>();
        SearchResponse response = requestBuilder.get();
        if (response.status() != RestStatus.OK) {
            logger.warn("Search status is no ok for " + requestBuilder);
            return new ServiceMultiResult<>(0, list);
        }

        for (SearchHit hit : response.getHits()) {
         //   System.out.println(hit.getSource());
            crIds.add(String.valueOf(hit.getSource().get(CRIndexKey.ID_KEY_CR)));
        }

        for (String id:crIds){
         CRReport crReport=  crReportRepository.findById(id);
         CRDTO crdto=new CRDTO();
         modelMapper.map(crReport,crdto) ;
         list.add(crdto);
        }

        return new ServiceMultiResult<>(list.size(), list);
        //return new ServiceMultiResult<>(response.getHits().totalHits, list);

    }


    @Override
    public ServiceResult<List<String>> suggest(String prefix) {
        CompletionSuggestionBuilder suggestion = SuggestBuilders.completionSuggestion("suggest").prefix(prefix).size(5);

        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("autocomplete", suggestion);

        SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME)
                .setTypes(INDEX_TYPE)
                .suggest(suggestBuilder);
        logger.debug(requestBuilder.toString());

        SearchResponse response = requestBuilder.get();
        Suggest suggest = response.getSuggest();
        if (suggest == null) {
            return ServiceResult.of(new ArrayList<>());
        }
        Suggest.Suggestion result = suggest.getSuggestion("autocomplete");

        int maxSuggest = 0;
        Set<String> suggestSet = new HashSet<>();

        for (Object term : result.getEntries()) {
            if (term instanceof CompletionSuggestion.Entry) {
                CompletionSuggestion.Entry item = (CompletionSuggestion.Entry) term;

                if (item.getOptions().isEmpty()) {
                    continue;
                }

                for (CompletionSuggestion.Entry.Option option : item.getOptions()) {
                    String tip = option.getText().string();
                    if (suggestSet.contains(tip)) {
                        continue;
                    }
                    suggestSet.add(tip);
                    maxSuggest++;
                }
            }

            if (maxSuggest > 5) {
                break;
            }
        }
        List<String> suggests = Lists.newArrayList(suggestSet.toArray(new String[]{}));
        return ServiceResult.of(suggests);
    }
}
